/**
 * 
 */
package org.statewalker.validator;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.statewalker.fsm.FsmProcess;
import org.statewalker.fsm.IFsmProcessListener;
import org.statewalker.fsm.providers.FsmStateProvider;
import org.statewalker.fsm.providers.FsmTransitionsConst;
import org.statewalker.fsm.providers.FsmTransitionsRegistry;
import org.statewalker.fsm.providers.IFsmTransitionsRegistry;
import org.statewalker.fsm.providers.conf.FsmTransitionsConfigurator;
import org.statewalker.fsm.providers.conf.IFsmTransitionsConfigurator;
import org.statewalker.graph.AbstractNodeWalker.Mode;

/**
 * @author kotelnikov
 */
public abstract class AbstractValidator<S extends IParseState, T extends IParseToken> {

    private IParseKey fAny;

    private FsmTransitionsConfigurator<IParseKey, IParseKey> fConfigurator;

    private IParseKey fError;

    private FsmTransitionsConst<IParseKey, IParseKey> fInfo;;

    private IFsmTransitionsRegistry<IParseKey, IParseKey> fRegistry = new FsmTransitionsRegistry<IParseKey, IParseKey>();

    private IParseKey fRoot;

    private FsmStateProvider<IParseKey, S, IParseKey, T> fStateProvider;

    public AbstractValidator() {
        super();
        fRoot = newParseKey("");
        fError = newParseKey("{error}");
        fAny = fRoot;
        IParseKey initialStateKey = newParseKey("{initial}");
        IParseKey finalStateKey = newParseKey("{final}");
        IParseKey defaultEventKey = fAny;
        fInfo = new FsmTransitionsConst<IParseKey, IParseKey>(
            initialStateKey,
            finalStateKey,
            defaultEventKey);
        fConfigurator = new FsmTransitionsConfigurator<IParseKey, IParseKey>(
            fInfo,
            fRegistry);
        fStateProvider = new FsmStateProvider<IParseKey, S, IParseKey, T>(
            fInfo,
            fRegistry) {

            @Override
            protected IParseKey getEventKey(T event) {
                return event.getKey();
            }

            @Override
            protected IParseKey getParentEventKey(IParseKey eventKey) {
                return eventKey.getParent();
            }

            @Override
            protected IParseKey getStateKey(S state) {
                return state != null ? state.getKey() : null;
            }

            @Override
            protected S newState(FsmProcess<S, T> process, IParseKey targetKey) {
                T token = process.getEvent();
                return AbstractValidator.this.newParseState(token, targetKey);
            }
        };
    }

    public void closeTag(FsmProcess<S, T> process, IParseKey key) {
        T token = newParseToken(key, false);
        onToken(process, token);
    }

    public IParseKey getERROR() {
        return fError;
    }

    protected IParseKey getFINAL() {
        return fInfo.getFinalStateKey();
    }

    public IFsmTransitionsConfigurator<IParseKey, IParseKey> getFsmConfigurator() {
        return fConfigurator;
    }

    protected IParseKey getINITIAL() {
        return fInfo.getInitialStateKey();
    }

    protected IParseKey getROOT() {
        return fRoot;
    }

    public abstract IParseKey newParseKey(String name);

    protected abstract S newParseState(T token, IParseKey targetKey);

    protected abstract T newParseToken(IParseKey key, boolean open);

    public FsmProcess<S, T> newProcess(
        IParseKey rootKey,
        IFsmProcessListener<S, T> stateListener) {
        S state = newParseState(null, rootKey);
        FsmProcess<S, T> process = new FsmProcess<S, T>(
            fStateProvider,
            stateListener,
            state);
        return process;
    }

    public void onToken(FsmProcess<S, T> process, T token) {
        boolean open = token.isOpen();
        Mode mode = null;
        if (open) {
            process.setEvent(token);
            mode = Mode.IN;
        } else {
            List<S> stack = process.getStateStack();
            for (S state : stack) {
                if (state.getKey().equals(token.getKey())) {
                    T finalToken = newParseToken(getFINAL(), true);
                    process.setEvent(finalToken);
                    mode = Mode.OUT;
                    break;
                }
            }
        }
        if (mode == null)
            return;

        Set<IParseKey> trace = new HashSet<IParseKey>();
        while (true) {
            Mode status = process.run();
            if (status == Mode.TERMINTATED)
                break;
            S currentState = process.getCurrentState();
            if (status == mode) {
                if (token.getKey().equals(currentState.getKey())) {
                    break;
                }
            }
            if (status == Mode.IN) {
                if (fError.equals(currentState))
                    break;
                IParseKey key = currentState.getKey();
                if (trace.contains(key))
                    throw new IllegalArgumentException("Infinite loop");
                trace.add(key);
            }
        }
    }

    public void openTag(FsmProcess<S, T> process, IParseKey key) {
        T token = newParseToken(key, true);
        onToken(process, token);
    }

    public void terminate(FsmProcess<S, T> process) {
        T finalToken = newParseToken(getFINAL(), true);
        process.setEvent(finalToken);
        while (true) {
            Mode status = process.run();
            if (status == Mode.TERMINTATED)
                break;
        }
    }
}
